;-------------------------------------------------------------------------
;  Copyright (C) 2010-2012 Christopher Brochtrup
;
;  This file is part of Capture2Text.
;
;  Capture2Text is free software: you can redistribute it and/or modify
;  it under the terms of the GNU General Public License as published by
;  the Free Software Foundation, either version 3 of the License, or
;  (at your option) any later version.
;
;  Capture2Text is distributed in the hope that it will be useful,
;  but WITHOUT ANY WARRANTY; without even the implied warranty of
;  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;  GNU General Public License for more details.
;
;  You should have received a copy of the GNU General Public License
;  along with Capture2Text.  If not, see <http://www.gnu.org/licenses/>.
;
;-------------------------------------------------------------------------

#NoEnv                       ; Recommended for performance and compatibility with future AutoHotkey releases.
SendMode Input               ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.
CoordMode, Mouse, Screen     ; Make 0,0 the top-left of screen
SetTitleMatchMode 2          ; Allow partial window title matches
FileEncoding, UTF-8          ; Use UTF-8 file encoding

#Include %A_ScriptDir%/ScreenCapture.ahk
#Include %A_ScriptDir%/Common.ahk
#Include %A_ScriptDir%/SettingsDlg.ahk
#Include %A_ScriptDir%/PopupDlg.ahk

IMG_CONV_UTIL       = Utils/ConvertImageFormat/ConvertImageFormat.exe ; The image conversion utility
OCR_UTIL_NHOCR      = Utils/nhocr/nhocr.exe         ; The NHocr OCR tool
OCR_UTIL_TESSERACT  = Utils/tesseract/tesseract.exe ; The Tesseract OCR tool
OCR_UTIL_TESSERACT_OLD  = Utils/tesseract/tesseract_3_01.exe ; Older Tesseract OCR tool (works better with Japanese)
SUBSTITUTIONS_FILE  = substitutions.txt
BOX_UPDATE_RATE     = 1    ; The update rate of the capture box (ms)
PREVIEW_UPDATE_RATE = 100  ; The ideal update rate of the preview box (ms). The OCR may take longer to perform though.
; Scale filter algorithm for OCR capture
; 0 = Nearest, 1 = Linear,   2 = Bilinear, 3 = Box,     4 = Triangle,
; 5 = Bell,    6 = B Spline, 7 = Lanczos,  8 = Mitchell
SCALE_FILTER        = 7

captureMode        = 0   ; 1 = In capture mode
topLeftCoordActive = 1   ; 0 = The top-left coordinate is active; 1 = The bottom-right coordinate is active
moveBothCorners    = 0   ; 1 = Move both corners at the same time
bothCornersHeight  = 0   ; The height of the capture box with in "move both corners" mode
bothCornersWidth   = 0   ; The width of the capture box with in "move both corners" mode
dictionary         = English ; The OCR dictionary to use
utteranceList      := [] ; Voice recognition results returned by Google
voiceCaptureMode   = 0   ; 1 = In voice capture mode
subList            := [] ; The substitutions list

; Create ocrDicTable and ocrCodeTable
createLanguageTable()

; Create installedDics
getInstalledDics()

; Read the settings in settings.ini
readSettings()
if (firstRun) {
    MsgBox, 初回起動ですが、先にCapture2Text.exeで設定を済ませてからお試しください。
    ExitApp
}

; Set the dictionary directory for the OCR tool
EnvSet, NHOCR_DICDIR, Utils/nhocr/Dic

; Read in the substitutions file
readsubstitutions()

numArg = %0% ; The number of command line arguments passed in by the user

;-------------------------------------------------------------------------

; 既に撮影したbmpファイルにOCRをかける
doOCR(inFile, outFile)
{
    global

    ; Capture the screen and OCR it
    ocrText := captureAndOCR_BMP(inFile, outFile)

    ; Replace certain characters in the text
    ocrText := replaceOCRText(ocrText)

    ; Output the OCR'd text based on user-defined settings (default is clipboard)
    doOutput(ocrText)

    ; If the user has specified a file to write the text to
    if(StrLen(outFile) > 0) {
        f := FileOpen(outFile, "w", 65001)
        f.Write(ocrText)
        f.Close()
    }
    return
}

; Display the language of the provided dictionary in the tray
displayOCRLanguageInTray(dic)
{
  global
  SetTrayTip(dic, 1000)
} ; displayOCRLanguageInTray


; Display the current text direction in the tray
displayTextDirectionInTray()
{
  global
  textDirectionMsg = %textDirection% text
  SetTrayTip(textDirectionMsg, 1000)

} ; displayTextDirectionInTray


; Get the size of a text control (in pixels)
; str    - The string displayed in the text control
; size   - The font size of the text
; font   - The font of the text
; height - 1 = return height as well as width, 0 = return only the width
getTextSize(str, size, font, height=false)
{
  global

  ; Creating a tempory hidden window, add the text, get size, delete window
  Gui, TextSizeWindow:Font, s%size%, %font%
  Gui, TextSizeWindow:Add, Text, vsizeText, %str%
  GuiControlGet outSize, TextSizeWindow:Pos, sizeText
  Gui, TextSizeWindow:Destroy

  Return height ? outSizeW "," outSizeH : outSizeW

} ; GetTextSize()


; Capture the screen and OCR it
; Parameters: integers; The screen coordinates that form the rectangle to capture
; Return Value: The OCR'd text
captureAndOCR_BMP(captureFile, outFile)
{
  global

  ;captureFile = Output/screen_capture.bmp ; Name of the captured image
  dictionaryCode := ocrDicTable[dictionary]
  ocrUtil := dic2OcrUtil(dictionary)

  ; Configure arguments based on OCR util
    ocrOutputFile = %outFile%-tmp.txt
  if(ocrUtil == OCR_UTIL_NHOCR) {
    EnvSet, NHOCR_DICCODES, %dictionaryCode% ; Set the dictionary for the OCR tool

   ;ocrOutputFile = Output/ocr.txt
    ocrInputFile = Output/screen_capture.ppm
    ocrUtilArgs = -block -o %ocrOutputFile% %ocrInputFile%
  }
  else
  {
   ;ocrOutputFile = Output/ocr
    ocrInputFile = Output/screen_capture.tif

    ; If the vertical text direction and Chinese or Japanese dictionary
    if((dictionary == "Japanese (Tesseract)")
       || (dictionary == "Chinese - Simplified (Tesseract)")
       || (dictionary == "Chinese - Traditional (Tesseract)"))
    {
      if(textDirection == "Vertical")
      {
        ; Assume a single uniform block of vertically aligned text.
        psmOption = -psm 5
      }
      else
      {
        ; Assume a single uniform block of text.
        psmOption = -psm 6
      }
    }
    else ; Horizontal text or not a Chinese or Japanese dictionary
    {
      ; Fully automatic page segmentation, but no OSD (Note: OSD is orientation detection). (Default)
      psmOption = -psm 3
    }

    whitelistArg =

    ; Determine if the whitelist should be used
    if (StrLen(whitelist) != 0)
    {
      whitelistArg = %WHITELIST_FILE%
    }

    ocrUtilArgs = %ocrInputFile% %ocrOutputFile% -l %dictionaryCode% %psmOption% %whitelistArg%
  }
  ; Convert the capture from bitmap to input required by the OCR tool
  RunWait, %IMG_CONV_UTIL% %captureFile% %ocrInputFile% %scaleFactor% %SCALE_FILTER%, , Hide

  ; Run OCR
  RunWait, %ocrUtil%  %ocrUtilArgs%, , Hide

  ; Read the OCR'd text from the OCR tools output
  ;ocrOutputFile = Output/ocr.txt
  FileRead, ocrText, %ocrOutputFile%

  return ocrText
} ; captureAndOCR


; Replace tokens with special characters
replaceSpecialChars(text)
{
  global

  StringReplace, text, text, ${cr}, `r, All
  StringReplace, text, text, ${lf}, `n, All
  StringReplace, text, text, ${tab}, %A_TAB%, All

  return text
} ; replaceSpecialChars


; Process the text before it is output.
; Parameters:
;   text - string; The OCR'd/Voice Recogized text
processTextBeforeOutput(text)
{
  global

  ; Add special characters to Prepend Text
  StringLen, length, prependText
  if(length > 0)
  {
    prependText := replaceSpecialChars(prependText)
  }

  ; Add special characters to Append Text
  StringLen, length, appendText
  if(length > 0)
  {
    appendText := replaceSpecialChars(appendText)
  }

  ; Append/Prepend text to the OCR'd text
  text = %prependText%%text%%appendText%

  return text

} ; processTextBeforeOutput


; Output the OCR'd text based on user-defined settings (default is clipboard)
; Parameters:
;   text - string; The OCR'd/Voice Recogized text
doOutput(text)
{
  global

  ; Do final text processing (append text, prepend text, etc.)
  text := processTextBeforeOutput(text)

  ; Save the text to the clipboard
  if(saveToClipboard)
  {
    clipboard = %text%
  }

  ; Show the text in a pop-up window
  if(popupWindow)
  {
    openOutputPopup(text, popupWindowWidth, popupWindowHeight)
  }

} ; doOutput

; Get the OCR tool that goes with a dictionary
; inDictionary - The language (not the dic code)
dic2OcrUtil(inDictionary)
{
  global

  if(inDictionary == "Japanese (NHocr)" or inDictionary == "Chinese (NHocr)")
  {
    theOcrUtil = %OCR_UTIL_NHOCR%
  }
  else if(inDictionary == "Japanese (Tesseract)")
  {
    theOcrUtil = %OCR_UTIL_TESSERACT_OLD%
  }
  else
  {
    theOcrUtil = %OCR_UTIL_TESSERACT%
  }

  return theOcrUtil

} ; dic2OcrUtil


; Replace certain characters in the OCR'd text
replaceOCRText(ocrText, force=false)
{
  global

  if(!preserveNewlines || force)
  {
    ; If Japanese or Chinese remove newlines, otherwise, replace them with spaces
    if(  (dictionary == "Japanese (Tesseract)")
      || (dictionary == "Japanese (NHocr)")
      || (dictionary == "Chinese (NHocr)")
      || (dictionary == "Chinese - Simplified (Tesseract)")
      || (dictionary == "Chinese - Traditional (Tesseract)"))
    {
      StringReplace, ocrText, ocrText, `r`n, , All
      StringReplace, ocrText, ocrText, `r, , All
      StringReplace, ocrText, ocrText, `n, , All
    }
    else ; Non-Japanese or Chinese language
    {
      StringReplace, ocrText, ocrText, `r`n, %A_Space%, All
      StringReplace, ocrText, ocrText, `r, %A_Space%, All
      StringReplace, ocrText, ocrText, `n, %A_Space%, All
    }
  }

  ; Trim whitesapce from start and end
  ocrText := Trim(ocrText, "`n")
  ocrText := Trim(ocrText, "`r")
  ocrText := Trim(ocrText, " ")

  Loop,2
    ocrText := performsubstitutions(ocrText)

  return ocrText

} ; replaceOCRText()


; Read in the substitutions file. Parses the "All:" and current OCR language sections
readsubstitutions()
{
  global
  Local line_count, last_dic

  ; Clear substitutions list
  subList := []

  ; File parse state
  ; 0 = Searching for All: section
  ; 1 = Looping through All: section
  ; 2 = Searching for current OCR language section
  ; 3 = Looping through current OCR language section
  ; 4 = Done
  state = 0
  line_count = 0
  last_dic =
  ; Read the substitutions file
  Loop, read, %SUBSTITUTIONS_FILE%
  {
    line_count++
    if(state == 0)
    {
      if(A_LoopReadLine == "All:")
      {
        state = 1
      }
    }
    else if((state == 1) || (state == 3))
    {
      StringLeft, firstChar, A_LoopReadLine, 1

      ; If the line is blank or is a comment
      if((StrLen(A_LoopReadLine) == 0) || (firstChar == "#"))
      {
        continue
      }

      ; If the line contains "="
      if(InStr(A_LoopReadLine, "="))
      {
        subList.Insert(A_LoopReadLine)
        last_dic := A_LoopReadLine
      }
      else
      {
        OutputDebug % "Read " line_count " lines for dictionary: " dictLine ". Last dict:" last_dic
        if(state == 1)
        {
          state = 2
        }
        else
        {
          state = 4
        }
      }
    }
    else if(state == 2)
    {
      dictLine = %dictionary%:

      if(A_LoopReadLine == dictLine)
      {
        state = 3
      }
    }
    else if(state == 4)
    {
      break
    }
  }

} ; readsubstitutions


; Perform substitutions on the provided text.
; Global inputs:
;   subList
performsubstitutions(text)
{
  global

    text := RegExReplace(text, "^胞",  "[@")
    text := RegExReplace(text, "^碑",  "[@")
    text := RegExReplace(text, "^Fr",  "[1")
    text := RegExReplace(text, "^\[e", "[@")

    text := RegExReplace(text, "`n胞", "`n[@")
    text := RegExReplace(text, "`n碑", "`n[@")

    text := RegExReplace(text, "\[@｜", "[@1")
    text := RegExReplace(text, "\[｜", "[1")

    text := RegExReplace(text, "([ァ-ヶー]+)L([ァ-ヶー]+)", "${1}ー${2}")

    text := RegExReplace(text, "([ァ-ヶー]+)7", "${1}ア")
    text := RegExReplace(text, "7([ァ-ヶー]+)", "ア${1}")

    text := RegExReplace(text, "([ァ-ヶー]+)茸", "${1}ダ")
    text := RegExReplace(text, "茸([ァ-ヶー]+)", "ダ${1}")

    ; 髪型や顔の見本の修正
    text := RegExReplace(text, "(本 N)[a-z][a-z]([1-9])", "${1}o.${2}")

    ; たっぷり すも7< ふ7<ら
    text := RegExReplace(text, "([ぁ-ー]+)7", "${1}っ")
    text := RegExReplace(text, "7([ぁ-ー]+)", "っ${1}")
    ; なり舌り → なりきり
    text := RegExReplace(text, "([ぁ-ー]+舌[ぁ-ー]+)", "${1}き${2}")

    ; ゴしルデン = ゴールデン
    text := RegExReplace(text, "([ァ-ヶ]+)し([ァ-ヶ]+)", "${1}ー${2}")

    ; 「力【ちから】」→カタカナのカ への補整
    text := RegExReplace(text, "([ァ-ヶー]+)力", "${1}カ")
    text := RegExReplace(text, "力([ァ-ヶー]+)", "カ${1}")

    ; 「バトル」への補整
    text := RegExReplace(text, "ノ[a-z\\'A-Z]*[ト卜]*[a-z\\'A-Z]*[ルレ]", "バトル")
    ; 前後にスペースの空いてしまった「ト」への補整
    text := RegExReplace(text, "([ァ-ヶー])[ ]+[ト卜][ ]+([ァ-ヶー])", "${1}ト${2}")

    ; カタカナの中のfl -> ト
    text := RegExReplace(text, "([ァ-ヶー])fl", "${1}ト")
    text := RegExReplace(text, "fl([ァ-ヶー])", "ト${1}")

    ; 耐久度の中の文字化けを数値に変更(左側)
    text := RegExReplace(text, "\(vo([0-9]*)/",      "(100${1}/")
    text := RegExReplace(text, "\(ij([0-9]*)/",      "(17${1}/")
    text := RegExReplace(text, "\(s([0-9]*)/",       "(3${1}/")
    text := RegExReplace(text, "\(([0-9]*)o/",       "(${1}0/")
    text := RegExReplace(text, "\(o([0-9]*)/",       "(0${1}/")
    text := RegExReplace(text, "\(([0-9]*)[ijl｜]/", "(${1}1/")
    text := RegExReplace(text, "\([ijl｜]([0-9]*)/", "(1${1}/")
    text := RegExReplace(text, "\([帥恥]o/",         "(600/")
    text := RegExReplace(text, "/l",                 "/1")
    text := RegExReplace(text, "\(l",                 "(1")

    ; 耐久度の中の文字化けを数値に変更(右側)
    text := RegExReplace(text, "/([0-9]*)o\)",       "(/${1}0)")
    text := RegExReplace(text, "/s([0-9]*)\)",       "(/3${1})")
    text := RegExReplace(text, "/([0-9]*)s\)",       "(/${1}3)")
    text := RegExReplace(text, "/[帥恥]o\)",          "/600)")

  ; Loop through substitutions list
  Loop % subList.MaxIndex()
  {
    ; Get current substitution
    curSub := % subList[A_Index]

    ; Extract the from and to portions of the substitution
    StringSplit, fields, curSub, =
    replaceFrom = %fields1%
    replaceTo = %fields2%

    ; Replace special tokens
    StringCaseSense, On
    StringReplace, replaceFrom, replaceFrom, `%cr`%, `r, All
    StringReplace, replaceTo, replaceTo, `%cr`%, `r, All

    StringReplace, replaceFrom, replaceFrom, `%eq`%, =, All
    StringReplace, replaceTo, replaceTo, `%eq`%, =, All

    StringReplace, replaceFrom, replaceFrom, `%lf`%, `n, All
    StringReplace, replaceTo, replaceTo, `%lf`%, `n, All

    StringReplace, replaceFrom, replaceFrom, `%perc`%, `%, All
    StringReplace, replaceTo, replaceTo, `%perc`%, `%, All

    StringReplace, replaceFrom, replaceFrom, `%space`%, %A_Space%, All
    StringReplace, replaceTo, replaceTo, `%space`%, %A_Space%, All

    StringReplace, replaceFrom, replaceFrom, `%tab`%, %A_Tab%, All
    StringReplace, replaceTo, replaceTo, `%tab`%, %A_Tab%, All

;   OutputDebug % replaceFrom "         " replaceTo

    ; Perform substitution
    StringReplace, text, text, %replaceFrom%, %replaceTo%, All
    StringCaseSense, Off
  }
    text := RegExReplace(text, "`nFr", "`n[1")
    text := RegExReplace(text, "`n\[e", "`n[@")

    ; 価格に含まれる数字以外の文字を、適当な数字に補正する。
    text := RegExReplace(text, "([0-9]+)Gold\]",   "${1} Gold]")    ; スペースがなかったら、空ける

    ; 価格の部分だけ、一旦切り出す。
    buf3=
    Loop, Parse, text, `n
    {
        endpos := InStr(A_LoopField, "Gold")
        if (endpos) {
            buf1 := SubStr(A_LoopField, endpos)
            StringLeft, buf2, A_LoopField, endpos-1
            buf2 := RegExReplace(buf2, "[vo]", "0")
            buf2 := RegExReplace(buf2, "[ijl｜ー]", "1")
            buf2 := RegExReplace(buf2, "[z]", "2")
            buf2 := RegExReplace(buf2, "[呼]", "[@2")
            buf2 := RegExReplace(buf2, "[ヰ]", "4")
            buf2 := RegExReplace(buf2, "[ty]", "4")
            buf2 := RegExReplace(buf2, "[s]", "5")
            buf2 := RegExReplace(buf2, "[i)B]", "6")
            buf2 := RegExReplace(buf2, "[ァ郎wW了]", "@7")
            buf2 := RegExReplace(buf2, "[Ff]", "[")
            buf2 := RegExReplace(buf2, "\[g", "[@")
            buf2 := RegExReplace(buf2, "[ag]", "9")
            buf2 := RegExReplace(buf2, "[¢sR]", "[2")
            buf2 := RegExReplace(buf2, "[%]", "[6")
            buf2 := RegExReplace(buf2, "[p]", "[7")
            ; 元の文字を連結
            buf3 .= buf2 . buf1 . "`n"
        } else {
            buf3 .= A_LoopField . "`n"
        }
    }
    text := buf3

  return text

} ; performsubstitutions


; Switch to the given dictionary. This sets the dictionary, menu check mark, and OCR util
switchToDic(dic)
{
  global
  dictionary := dic

  ; Read the substitutions file for the new language
  readsubstitutions()

} ; switchToDic


; Toggle text direction
toggleTextDirection()
{
  global

  if(textDirection == "Vertical")
  {
    textDirection = Horizontal
  }
  else
  {
    textDirection = Vertical
  }
} ; toggleTextDirection


; Set the tray tip
SetTrayTip(txt, timeout)
{
  global

  TrayTip, , %txt%
  SetTimer, RemoveTrayTip, %timeout%

} ; SetTrayTip


; Used with SetTrayTip to remove the tray tip
RemoveTrayTip:
  SetTimer, RemoveTrayTip, Off
  TrayTip
return ; RemoveTrayTip:
